<?php

/**
 * 系统通知
 *
 * @package    modules
 * @subpackage Notice
 * @author     苏宁 <snsnsky@gmail.com>
 *
 * $Id$
 */

namespace Notice;

use Common\Bridge;
use Queue\Queue;
use Mobile\AppPush;
use Center\User as CUser;

class Sys
{
    const TID_MSG_SHOP = 1; // 商家有礼消息
    const TID_MSG_GROUP = 2; // 低价团消息

    const TYPE_MSG_ACT_OVER = 1; // 活动结果即将公布通知
    const TYPE_MSG_ACT_RESULT = 2; // 结果公示通知
    const TYPE_MSG_ACT_REWARD = 3; // 活动获奖通知
    const TYPE_MSG_INVITE = 4; // 邀请人消息
    const TYPE_MSG_SYS = 5; // 系统消息
    const TYPE_MSG_DONATE = 6; // 赠送金果
    const TYPE_MSG_COUPON = 7; // 电子券获奖通知

    /**
     * DB 位置
     */
    const REDIS_DB = 0;
    const PUSH_TIMES_REDIS_DB = 1; // 推送次数限制
    // 进贡推送次数限制
    const REDIS_KEY_PUSH_TIMES = 'PT:%d';
    /**
     * 时间限制KEY
     */
    const REDIS_KEY_TIME_LIMIT = 'T:%d:%d';
    /**
     * 消息数量KEY
     */
    const REDIS_KEY_NUM = 'N:%d';
    /**
     * 消息内容KEY
     */
    const REDIS_KEY_NOTICE = 'L:%d:%d';
    // 消息红点
    const REDIS_KEY_DOT = 'D:%d';
    /**
     * 消息最大数量
     */
    const REDIS_LIST_MAX = 1000;
    /**
     * 消息分组
     */
    const REDIS_GROUP_SHOP_MSG = self::TID_MSG_SHOP;
    const REDIS_GROUP_GROUP_MSG = self::TID_MSG_GROUP;
    /**
     * 分组列表
     */
    const GROUP_LIST = [
        self::REDIS_GROUP_SHOP_MSG => self::REDIS_GROUP_SHOP_MSG,
        self::REDIS_GROUP_GROUP_MSG => self::REDIS_GROUP_GROUP_MSG,
    ];

    const STRUCT_MSG = [
        'tid' => 'tid', // 消息分类ID
        'type' => 'type', // 消息类型ID
        'activeid' => 'activeid', //活动ID
        'shopid' => 'shopid', //商铺ID
        'cont' => 'cont', // 消息内容
        'userid' => 'userid', //邀请的用户id
        'itemid' => 'itemid', //图文消息id等
        'url' => 'url', // 跳转链接地址
        't' => 't' // 时间戳
    ];


    /**
     * 消息计数字段
     */
    const REDIS_NUM_FIELD_SHOP_MSG = 'shop';
    const REDIS_NUM_FIELD_GROUP_MSG = 'group';

    /**
     * 计数字段列表
     */
    const FIELD_NUM_LIST = [
        self::REDIS_NUM_FIELD_SHOP_MSG => self::REDIS_NUM_FIELD_SHOP_MSG,
        self::REDIS_NUM_FIELD_GROUP_MSG => self::REDIS_NUM_FIELD_GROUP_MSG
    ];

    const GROUP_NUM_LIST = [
        self::REDIS_GROUP_SHOP_MSG => self::REDIS_NUM_FIELD_SHOP_MSG,
        self::REDIS_GROUP_GROUP_MSG => self::REDIS_NUM_FIELD_GROUP_MSG,
    ];

    /**
     * 添加一条
     *
     * @param int|array $uids   用户ID, 可以多个
     * @param int       $group  分组ID, self::REDIS_GROUP_*
     * @param array     $struct 数据结构, 参见self::STRUCT_*
     * @return bool
     */
    public static function addNotice($uids, $group, $struct)
    {
        $uids = is_array($uids) ? $uids : [$uids];
        switch ($group) {
            case self::REDIS_GROUP_SHOP_MSG:
                $notice = array_intersect_key($struct, self::STRUCT_MSG);

                if (count($notice) != count(self::STRUCT_MSG)) {
                    return false;
                }
                $field = self::REDIS_NUM_FIELD_SHOP_MSG;

                break;
            case self::REDIS_GROUP_GROUP_MSG:
                $notice = array_intersect_key($struct, self::STRUCT_MSG);

                if (count($notice) != count(self::STRUCT_MSG)) {
                    return false;
                }
                $field = self::REDIS_NUM_FIELD_GROUP_MSG;

                break;
            default:
                return false;
        }

        //无需合并。直接写入Redis
        $redis = Bridge::redis(Bridge::REDIS_NOTICE_W, self::REDIS_DB);
        $redis->multi(\Redis::PIPELINE);

        $id_map = [];
        $count = 0;
        foreach ($uids as $v) {
            $key = sprintf(self::REDIS_KEY_NOTICE, $v, $group);
            $redis->lPush($key, serialize($notice));
            $redis->lTrim($key, 0, self::REDIS_LIST_MAX - 1);
            $redis->hIncrBy(sprintf(self::REDIS_KEY_NUM, $v), $field, 1);
            $redis->hMset(sprintf(self::REDIS_KEY_DOT, $v), [$v => 1]); // 红点设置

            $count += 3;
            $id_map[$count] = $v;
        }
        $ret = $redis->exec();
        $redis->multi(\Redis::PIPELINE);
        foreach ($ret as $k => $v) {
            $x = $k + 1;
            if ($x % 3 == 0 && $v > self::REDIS_LIST_MAX) {
                $redis->hMset(sprintf(self::REDIS_KEY_NUM, $id_map[$x]), [$field => self::REDIS_LIST_MAX]);
            }
        }
        $redis->exec();

        // 消息推送
        self::sendPush($group, $struct, $uids);

        return true;
    }

    /**
     * 获取通知列表
     *
     * @param int $uid    用户ID
     * @param int $group  分组ID, self::REDIS_GROUP_*
     * @param int $offset 偏移量
     * @param int $limit  总数
     * @return array|bool 成功返回数组, 参见self::STRUCT_*
     */
    public static function getNotice($uid, $group, $offset = 0, $limit = 99)
    {
        $groups = self::GROUP_LIST;
        if (!isset($groups[$group])) {
            return false;
        }

        $key = sprintf(self::REDIS_KEY_NOTICE, $uid, $group);

        $redis = Bridge::redis(Bridge::REDIS_NOTICE_R, self::REDIS_DB);
        $list = $redis->lRange($key, $offset, $limit);
        foreach ($list as & $v) {
            $v = unserialize($v);
        }

        return $list;
    }

    /**
     * 清空通知列表
     *
     * @param int $uid   用户ID
     * @param int $group 分组ID, self::REDIS_GROUP_*
     * @return bool
     */
    public static function clearNotice($uid, $group)
    {
        $groups = self::GROUP_LIST;
        if (!isset($groups[$group])) {
            return false;
        }

        $key = sprintf(self::REDIS_KEY_NOTICE, $uid, $group);
        $redis = Bridge::redis(Bridge::REDIS_NOTICE_W, self::REDIS_DB);
        $redis->del($key);

        $group_num_list = self::GROUP_NUM_LIST;
        self::setNum($uid, $group_num_list[$group], 0);

        return true;
    }

    /**
     * 设置未读消息数量
     *
     * @param int        $uid  用户ID
     * @param int        $type 类型, 见self::FIELD_NUM_LIST;
     * @param int|string $num  数量, 可以是 [count|'-count'|'+count']
     * @return bool|int  如果是设置, 返回bool, 如果是递增, 返回递增后的数
     */
    public static function setNum($uid, $type, $num)
    {
        $uid = (int)$uid;
        $type_list = self::FIELD_NUM_LIST;
        $key = sprintf(self::REDIS_KEY_NUM, $uid);

        if (!is_numeric($num)) {
            return false;
        }

        if (!isset($type_list[$type])) {
            return false;
        }

        $flag = substr($num, 0, 1);
        $field = $type;

        $redis = Bridge::redis(Bridge::REDIS_NOTICE_W, self::REDIS_DB);
        if ($flag == '+' || $flag == '-') {
            return $redis->hIncrBy($key, $field, $num);
        }

        return $redis->hMset($key, [$field => (int)$num]);
    }

    /**
     * 检查(提醒)是否存在时间限制
     *
     * @param int $uid      用户ID
     * @param int $from_uid 来源用户ID, 当为REDIS_GROUP_TODO时有效
     * @param int $babyid   宝宝ID
     * @return bool true: 是, false: 否
     */
    public static function checkTimeLimit($uid, $from_uid, $babyid = 0)
    {
        $redis = Bridge::redis(Bridge::REDIS_NOTICE_R, self::REDIS_DB);
        $time = $redis->get(sprintf(self::REDIS_KEY_TIME_LIMIT, $from_uid, $uid, $babyid));
        if (!empty($time)) {
            return true;
        }

        return false;
    }

    /**
     * 获得全部消息数量
     *
     * @param int $uid   用户ID
     * @param int $group 分组ID, self::REDIS_GROUP_*
     * @return int 消息数量
     */
    public static function getAllNum($uid, $group)
    {
        $redis = Bridge::redis(Bridge::REDIS_NOTICE_R, self::REDIS_DB);
        $key = sprintf(self::REDIS_KEY_NOTICE, $uid, $group);

        return $redis->lLen($key);

    }

    /**
     * 获得未读消息数量
     *
     * @param int $uid 用户ID
     * @return array 见 self::FIELD_NUM_LIST
     */
    public static function getNum($uid)
    {
        $redis = Bridge::redis(Bridge::REDIS_NOTICE_R, self::REDIS_DB);

        return $redis->hGetAll(sprintf(self::REDIS_KEY_NUM, $uid));
    }


    /**
     * 向所有用户发送通知
     *
     * @param     $group
     * @param     $struct
     * @param int $from_uid
     * @return bool
     */
    public static function sendAll($group, $struct, $from_uid = 0)
    {
        set_time_limit(0);
        //每次发送人数为100
        $num = 100;

        $notice = array_intersect_key($struct, self::STRUCT_MSG);
        if (count($notice) != count(self::STRUCT_MSG)) {
            return false;
        }

        $pdo = Bridge::pdo(Bridge::DB_USER_W);
        $count = $pdo->select("MAX(UserId)")->from("User")->getValue();
        $cnt = ceil($count / $num);
        for ($i = 0; $i < $cnt; $i++) {
            $pdo->clear();
            $res = $pdo->select("UserId")->from("User")->where([
                "UserId >=" => $i * $num,
                "UserId <" => ($i + 1) * $num,
            ])->order("UserId")->getAll();
            //发送队列
            $userids = array_column($res, 'UserId');

            if (count($userids) > 0 && is_array($userids)) {
                $queue_obj = self::_getQueue();
                $data = [
                    'uids' => $userids,
                    'group' => $group,
                    'notice' => $notice,
                    'fromuid' => $from_uid,
                ];
                $queue_obj->add('SendNotice', $data);
            }
        }

        //发送消息推送
        sleep(5); //延迟推送 5s
        self::sendPush($group, $struct);

        return true;
    }

    /**
     * 获得队列实例
     *
     * @return null|Queue
     */
    private static function _getQueue()
    {
        static $queue_obj = null;

        if ($queue_obj === null) {
            $queue_obj = new Queue();
        }

        return $queue_obj;
    }


    /**
     * 推送一条/多条同一类型的消息
     *
     * @param int       $group  分组ID, self::REDIS_GROUP_*
     * @param array     $struct 数据结构, 参见self::STRUCT_*
     * @param int|array $uids   用户ID, 可以多个
     */
    public static function sendPush($group, $struct, $uids = [])
    {

        $task = loadconf('mobileapi/task')[$group];
        $data = [];
        $type = AppPush::PUSH_TYPE_MSG;
        $cont = $task[$struct['type']]['TaskName'];
        $msg = !empty($struct['cont']) ? $struct['cont'] : $cont;
        $scheme = $struct['url'];
        switch ($struct['type']) {
            case self::TYPE_MSG_ACT_OVER:
                $type = 0;
                break;
            case self::TYPE_MSG_ACT_RESULT:
                $data[AppPush::VAR_ID] = $struct['activeid'];
                $type = AppPush::PUSH_TYPE_ACTIVE_RESULT;
                break;
            case self::TYPE_MSG_ACT_REWARD:
                $data[AppPush::VAR_ID] = $struct['activeid'];
                $type = AppPush::PUSH_TYPE_PRIZE_RESULT;
                break;
            case self::TYPE_MSG_INVITE:
                $type = 0;
                break;
            case self::TYPE_MSG_SYS:
                $data[AppPush::VAR_URL] = $struct['url'];
                $type = AppPush::PUSH_TYPE_URL;
                break;
            case self::TYPE_MSG_DONATE:
                $type = AppPush::PUSH_TYPE_WALLET;
                break;
            case self::TYPE_MSG_COUPON:
                $data[AppPush::VAR_ID] = $struct['url'];
                $type = AppPush::PUSH_TYPE_TICKET_RESULT;
                break;
        }
        if (!empty($uids)) {
            $uids = is_array($uids) ? $uids : [$uids];
            foreach ($uids as $uid) {
                if (!empty($msg) && !empty($type)) {
                    AppPush::pushByUserId($uid, $type, $msg, $scheme);
                }
            }
        } else {
            if (!empty($msg) && !empty($type)) {
                AppPush::pushAll($type, $msg, $scheme);
            }
        }
    }

    /**
     * 获取推送次数
     *
     * @param $uid
     * @return bool|string
     */
    public static function getPushTimes($uid)
    {
        $redis = Bridge::redis(Bridge::REDIS_NOTICE_R, self::PUSH_TIMES_REDIS_DB);
        $key = sprintf(self::REDIS_KEY_PUSH_TIMES, $uid);

        return $redis->get($key);
    }

    /**
     *
     */
    public static function setPushTimes($uid)
    {
        $redis = Bridge::redis(Bridge::REDIS_NOTICE_W, self::PUSH_TIMES_REDIS_DB);
        $key = sprintf(self::REDIS_KEY_PUSH_TIMES, $uid);
        $timeout = strtotime(date('Y-m-d', strtotime('+ 1 day'))) - time();

        return $redis->set($key, 1, $timeout);
    }
}
